Passive, dependency-free detector for the wp2shell WordPress Core RCE chain (CVE-2026-63030 route confusion + CVE-2026-60137 SQL injection).
Python 3.8+ · stdlib only · read-only / passive · MIT
Point it at a list of hosts. It tells you, in color, which ones are running an affected WordPress version, and never touches the vulnerable code path to do so.
$ ./wp2shell-detect -i estate.txt
VULNERABLE https://blog.example.com ver 7.0.1 batch yes conf 100% meta=7.0.1,rss=7.0.1
NOT VULN https://shop.example.com ver 7.0.2 batch yes conf 100% meta=7.0.2,asset=7.0.2
SQLI-ONLY https://old.example.com ver 6.8.3 batch no conf 70% rss=6.8.3
UNKNOWN https://hardened.example ver - batch yes conf - WordPress detected but version hidden
NOT WP https://api.example.com ver - batch - conf -
For wp2shell, the core version is the authoritative gate: the vulnerable
code exists in 6.9.0-6.9.4 and 7.0.0-7.0.1 and nowhere else. You do not need
to fire the exploit to answer "is this host affected?"; you need the version.
So this tool sends only benign GET requests and fingerprints the version from
public signals. That is a deliberate choice:
- Safe to run across a whole estate. No attack traffic, no risk of tripping a live blind-SQLi path or leaving artifacts.
- Defensible. You can point it at production and at partners' assets (with authorization) without the "am I attacking this?" question.
- Legal footing. Passive version fingerprinting is ordinary web traffic, indistinguishable from a normal crawl.
If you need dynamic proof on a system you own, stand up an affected build in an isolated lab and validate there. This tool is for inventory and triage at scale.
- Fingerprints the WordPress version from four independent signals, cross-checked:
<meta name="generator">on the homepage- the
<generator>tag in/feed/(RSS) /readme.html(when not stripped)?ver=on core assets underwp-includes/wp-admin
- Reports a confidence score based on how many signals agree.
- Notes whether the REST batch route is advertised in
/wp-json/(context, not a probe). - Classifies each host and prints a color table plus a summary.
- Optional JSON output for pipelines; exit code
2if any host is in the RCE range.
- No batch route-confusion payload.
- No SQL injection probe (no time-based, no boolean-based).
- Nothing that reaches
serve_batch_request_v1()orWP_Querywith hostile input.
No dependencies. Clone and run:
git clone https://github.com/christbowel/wp2shell-detector.git
cd wp2shell-detector
chmod +x wp2shell-detect.py
./wp2shell-detect.py --helpwp2shell-detect [targets ...] [-i FILE] [options]
-i, --input FILE one target per line (# comments allowed)
-t, --threads N concurrency (default 10)
--timeout SEC per-request timeout (default 8)
--delay SEC politeness delay per worker
--insecure skip TLS certificate verification
--no-color disable ANSI colors
--json machine-readable output
-o, --output FILE write JSON results to FILE
Examples:
# a few hosts
./wp2shell-detect.py https://a.example https://b.example
# a full estate, 20 workers, JSON to disk
./wp2shell-detect.py -i estate.txt -t 20 -o results.json
# nightly cron: exit 2 if anything is exposed
./wp2shell-detect.py -i estate.txt --no-color || alert "wp2shell exposure found"| Verdict | Meaning |
|---|---|
VULNERABLE |
Version in 6.9.0-6.9.4 or 7.0.0-7.0.1: full RCE chain. Patch now. |
SQLI-ONLY |
Version in 6.8.0-6.8.5: SQLi present, route confusion absent. Still patch. |
NOT VULN |
Fixed or unaffected version for this chain. |
UNKNOWN |
WordPress detected, version hidden or stripped. Verify with wp core version. |
NOT WP |
No WordPress fingerprint. |
UNREACHABLE |
Host did not respond. |
UNKNOWN is not a clean bill of health. A hardened install that strips its
version is exactly where you should confirm manually.
| Branch | Affected | Fixed in |
|---|---|---|
| 6.8.x | 6.8.0 - 6.8.5 (SQLi only) | 6.8.6 |
| 6.9.x | 6.9.0 - 6.9.4 | 6.9.5 |
| 7.0.x | 7.0.0 - 7.0.1 | 7.0.2 |
| 7.1 | beta before beta2 | 7.1-beta2 |
- Patch. Update to
7.0.2,6.9.5, or6.8.6. Then verify the version actually landed (wp core version): forced auto-updates do not reach installs with auto-update disabled, nor version-controlled or frozen deployments. - Virtual patch at the edge (buys time across the whole estate in one move).
Block anonymous access to both forms of the route:
/wp-json/batch/v1?rest_route=/batch/v1or require authentication on the batch endpoint via therest_pre_dispatchfilter. This can break legitimate batch integrations, so monitor and allowlist.
- Assume breach on exposed, unpatched hosts. Public PoCs exist and early
in-the-wild activity has been reported. Hunt for: unexpected admin accounts,
newly added or modified plugins (webshell upload is the RCE vector), recently
changed PHP files under
wp-content, and anomalousPOST /batch/v1traffic in access logs. On EDR, watch for the web process spawning a shell. - Do not rely on an object cache as protection. It may affect exploitation reliability; it does not close the vulnerability.
This tool is for authorized assessment only. Run it against assets you own
or have explicit written permission to test. Passive or not, scanning third
parties without authorization may be unlawful in your jurisdiction. Use the
--delay and --threads options to stay polite. The authors accept no
liability for misuse.
- https://christbowel.com/blog/wp2shell/
- Searchlight Cyber / Assetnote advisory (details withheld)
- WordPress 7.0.2 release announcement
- GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf
- CVE-2026-63030, CVE-2026-60137
MIT.
